package com.googlecode.ddlutilstool.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


/**
 * <p>A simple INI properties container.  A INI property file
 * is a properties files with properties grouped under headings.</p>
 * 
 * <p>Each heading name is called a <i>group</i>.</p>
 * 
 * <p>The format of the file is:</p>
 * 
 * <pre>
 * #comment
 * [group1]
 * key1=val1
 * key2=val2
 * [group2]
 * key3=val3
 * </pre>
 * 
 * @author critchie
 *
 */
public class INIProperties {

	protected static String EOL = System.getProperty("line.separator", "\n");
	
	//A map keyed by group, with each value being a standard properties file
	private HashMap<String,HashMap<String,String>> mGroups;
	
	/**
	 * <p>Builds an empty properties.</p>
	 */
	public INIProperties() {
		mGroups = new HashMap<String, HashMap<String,String>>();
	}
	
	/**
	 * <p>Adds a property to a group.  It will create
	 * the group if it does not already exist.</p>
	 * @param group the group name
	 * @param key the property key
	 * @param value the property value
	 */
	public void setProperty(String group, String key, String value) {
		HashMap<String,String> groupMap = mGroups.get(group);
		if (groupMap==null) {
			groupMap = new HashMap<String, String>();
			mGroups.put(group, groupMap);
		}
		groupMap.put(key, value);
	}
	
	/**
	 * <p>Searches for a property under the given
	 * group, and returns its value if found.</p>
	 * 
	 * @param group the group name
	 * @param key the property key
	 * @return The property value, if found.
	 */
	public String getProperty(String group, String key) {
		HashMap<String,String> groupMap = mGroups.get(group);
		if (groupMap!=null) {
			return groupMap.get(key);
		}
		return null;
	}
	
	/**
	 * <p>Gets the full list of properties under a
	 * group</p>
	 * 
	 * @param group the group name
	 * @return the set of properties
	 */
	public Map<String,String> getGroup(String group) {
		return mGroups.get(group);
	}
	
	/**
	 * @return a list of all the group names.
	 */
	public Set<String> getGroupNames() {
		return mGroups.keySet();
	}
	
	/**
	 * <p>Removes the entire group, including
	 * all of its properties.  If the group is
	 * not found, it does nothing.</p>
	 * 
	 * @param group the group name
	 */
	public void removeGroup(String group) {
		mGroups.remove(group);
	}
	
	/**
	 * <p>Removes one property from a group.
	 * If the property or group is not found,
	 * it does nothing.</p>
	 * 
	 * @param group the group name
	 * @param key the property key
	 */
	public void removeProperty(String group, String key) {
		HashMap<String,String> groupMap = mGroups.get(group);
		if (groupMap!=null) {
			groupMap.remove(key);
		}
	}
	
	/**
	 * @return Totals the number of entries.  A group is an entry, and each property in the group is an entry.
	 */
	public int getSize() {
		int total = 0;
		for (String group : mGroups.keySet()) {
			total++;
			Map<String,String> props = getGroup(group);
			if (props!=null) {
				total+=props.size();
			}
		}
		return total;
	}
	
	/**
	 * <p>Will output the multiline string containing
	 * the groups and properties in the format
	 * defined at the top of this class.</p>
	 * 
	 * Override
	 * @see java.lang.Object#toString()
	 */
    public String toString() {
		StringWriter writer = new StringWriter();
		try {
			write(this,writer);
		}
		catch (IOException e) {
			e.printStackTrace(new PrintWriter(writer));
		}
		return writer.toString();
	}

	/**
	 * <p>Removes all white space before and after the given string.</p>
	 * 
	 * @param pString the string to remove whitespace from
	 * @return the string without the whitespace
	 */
	public static String trimString(String pString) {
		if (pString==null) {
			return null;
		}
		char[] chars = pString.toCharArray();
		int left=0;
		int right=pString.length()-1;
		while (left<pString.length() && Character.isWhitespace(chars[left])) {
			left++;
		}
		while (right>=left && Character.isWhitespace(chars[right])) {
			right--;
		}
		if (left>right) {
			return "";
		}
		return pString.substring(left,right+1);
	}
	
	/**
	 * <p>Writes out the INI properties in a standard INI format
	 * to the given Writer.</p>
	 * 
	 * <p>The format of the file is:</p>
	 * 
	 * <pre>
	 * #comment
	 * [group1]
	 * key1=val1
	 * key2=val2
	 * [group2]
	 * key3=val3
	 * </pre>
	 * 
	 * @param pProperties the properties to write out
	 * @param pWriter the destination
	 * @throws IOException There was a problem writing to the Writer
	 */
    public static void write(INIProperties pProperties, Writer pWriter) throws IOException {
    	PrintWriter out = new PrintWriter(pWriter);
    	for (String group : pProperties.getGroupNames()) {
    		//String fixedGroup = saveConvert(group, true, true);
    		out.print("[");
    		out.print(group);
    		out.print("]");
    		out.println();
    		
    		Map<String,String> properties = pProperties.getGroup(group);
    		for (String key : properties.keySet()) {
    			String value = properties.get(key);
    			
    			//key = saveConvert(key, true, true);
    			//value = saveConvert(value, false, true);
    			out.print(key);
    			out.print("=");
    			out.print(value);
    			out.println();
    		}
    		out.println();
    	}
	}
    
    /**
     * <p>Reads a formatted INI file and creates the
     * INIProperties.</p>
     * 
	 * <p>The format of the file is:</p>
	 * 
	 * <pre>
	 * #comment
	 * [group1]
	 * key1=val1
	 * key2=val2
	 * [group2]
	 * key3=val3
	 * </pre>
	 * 
     * @param pReader the input source
     * @return A created INIProperties using the contents of the Reader
     * @throws IOException There was a problem reading from the Reader
     */
    public static INIProperties read(InputStream pReader) throws IOException {
    	return read(new InputStreamReader(pReader));
    }
    
    /**
     * <p>Reads a formatted INI file and creates the
     * INIProperties.</p>
     * 
	 * <p>The format of the file is:</p>
	 * 
	 * <pre>
	 * #comment
	 * [group1]
	 * key1=val1
	 * key2=val2
	 * [group2]
	 * key3=val3
	 * </pre>
	 * 
     * @param pReader the input source
     * @return A created INIProperties using the contents of the Reader
     * @throws IOException There was a problem reading from the Reader
     */
   public static INIProperties read(Reader pReader) throws IOException {
    	INIProperties properties = new INIProperties();
    	
    	String currentGroup = "unspecified";
       	BufferedReader reader = new BufferedReader(pReader);
       	String line = reader.readLine();
       	while (line!=null) {
       		line = trimString(line);
       		
       		//check for comments
   			int commentStart=-1;
   			char[] chars = line.toCharArray();
   			for (int x=0; x<chars.length && commentStart<0; x++) {
   				if (chars[x]=='#' && (x==0 || chars[x-1]!='\\')) {
   					commentStart = x;
   				}
   			}
   			if (commentStart>=0) {
   				line = line.substring(0,commentStart);
   			}
   			
       		if (line.length()>0) {
       			
       			if (line.startsWith("[") && line.endsWith("]")) {
       				//we have a group!
       				currentGroup = line.substring(1,line.length()-1);
       			}
       			else {
       	   			int equals=-1;
       	   			for (int x=0; x<chars.length && equals<0; x++) {
       	   				if (chars[x]=='=' && (x==0 || chars[x-1]!='\\')) {
       	   					equals = x;
       	   				}
       	   			}
       	   			if (equals>=0) {
       	   				String key = line.substring(0,equals);
       	   				String value = line.substring(equals+1);
       	   				
       	   				
       	   				//key = loadConvert(key);
       	   				//value = loadConvert(value);
       	   				properties.setProperty(currentGroup, key, value);
       	   			}
       	   			else {
       	   				throw new IOException("Unexpected line: "+line);
       	   			}
       			}
       		}
       		line = reader.readLine();
       	}
       	return properties;
    }
    
}
